package joapp

import (
	"encoding/json"
	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
	"os"
	"sync"
)

type IAppCfg interface {
	Test() error
	FixDefaultValues() error
}

type AppCfgMgr[TCfg IAppCfg] struct {
	cfgInstance TCfg
	locker      *sync.RWMutex
}

func NewAppCfgMgr[TCfg IAppCfg](cfg TCfg) *AppCfgMgr[TCfg] {
	return &AppCfgMgr[TCfg]{
		cfgInstance: cfg,
		locker:      new(sync.RWMutex),
	}
}

func (mgr *AppCfgMgr[TCfg]) LoadYamlCfgFromFile(cfgFilePath string) error {
	return mgr.loadConfigFromFileImpl(cfgFilePath, configFileTypeYaml)
}

func (mgr *AppCfgMgr[TCfg]) LoadJsonCfgFromFile(cfgFIlePath string) error {
	return mgr.loadConfigFromFileImpl(cfgFIlePath, configFileTypeJson)
}

func (mgr *AppCfgMgr[TCfg]) ReloadConfig(cfg TCfg) error {
	if err := cfg.Test(); err != nil {
		return errors.Wrap(err, "try test config fail")
	}

	if err := cfg.FixDefaultValues(); err != nil {
		return errors.Wrap(err, "try fix default values fail")
	}

	mgr.locker.Lock()
	defer mgr.locker.Unlock()
	mgr.cfgInstance = cfg
	return nil
}

func (mgr *AppCfgMgr[TCfg]) GetCfg() TCfg {
	mgr.locker.RLock()
	defer mgr.locker.RUnlock()

	return mgr.cfgInstance
}

func (mgr *AppCfgMgr[TCfg]) SaveToJsonFile(filePath string) error {
	return mgr.trySaveConfigToFileImpl(filePath, configFileTypeJson)
}

func (mgr *AppCfgMgr[TCfg]) SaveToYamlFile(filePath string) error {
	return mgr.trySaveConfigToFileImpl(filePath, configFileTypeYaml)
}

const (
	configFileTypeJson = iota
	configFileTypeYaml
)

func (mgr *AppCfgMgr[TCfg]) loadConfigFromFileImpl(cfgFilePath string, fileType int) error {
	if data, err := os.ReadFile(cfgFilePath); err != nil {
		return errors.Wrapf(err, "load config file '%s' fail", cfgFilePath)
	} else {
		mgr.locker.Lock()
		defer mgr.locker.Unlock()

		switch fileType {
		case configFileTypeYaml:
			if err = yaml.Unmarshal(data, mgr.cfgInstance); err != nil {
				return errors.Wrapf(err, "try unmarshal config file '%s' fail", cfgFilePath)
			}
		case configFileTypeJson:
			if err = yaml.Unmarshal(data, mgr.cfgInstance); err != nil {
				return errors.Wrapf(err, "try unmarshal config file '%s' fail", cfgFilePath)
			}
		default:
			return errors.Errorf("config file type %d not support", fileType)
		}

		return nil
	}
}

func (mgr *AppCfgMgr[TCfg]) trySaveConfigToFileImpl(cfgFilePath string, fileType int) error {
	if cfgFilePath == "" {
		return errors.New("dest config file path is empty")
	}

	var data []byte
	switch fileType {
	case configFileTypeYaml:
		if d, e := yaml.Marshal(mgr.cfgInstance); e != nil {
			return errors.Wrap(e, "try marshal config data fail")
		} else {
			data = d
		}
	case configFileTypeJson:
		if d, e := json.Marshal(mgr.cfgInstance); e != nil {
			return errors.Wrap(e, "try marshal config data fail")
		} else {
			data = d
		}
	default:
		return errors.Errorf("config file type %d not support", fileType)
	}

	if err := os.WriteFile(cfgFilePath, data, 0644); err != nil {
		return errors.Wrapf(err, "save config file '%s' fail", cfgFilePath)
	}

	return nil
}
